import math

import numpy as np
import cv2

"""
A module for storing cell's information
each cell object has information for further process:
ID, position of centroid, path, color, area, bounding rectangular, perimeter and splitting status
"""


def save_cells_info(contours, colors):
    cell_list = []
    for i in range(0, len(contours)):
        x, y, w, h = cv2.boundingRect(contours[i])
        # filter cells around image boundaries
        if x > 2 and y > 2 and x + w < 1100 - 2 and y + h < 700 -2:
            cell = Cell(contours[i], colors[i])
            cell_list.append(cell)
    return cell_list


def get_cell_by_centroid(cells, centroid):
    for c in cells:
        if c.get_centroid() == centroid:
            return c
    return False


def get_max_id(cells):
    max_id = -1
    for c in cells:
        if max_id < c.get_id():
            max_id = c.get_id()
    return max_id


# Cell class save all information of a cell, including analysing method
class Cell:
    def __init__(self, contour, color, is_splitting=False):
        self.id = -1  # -1: uncertain ID
        self.contour = contour
        self.is_splitting = is_splitting
        self.segmentation_color = color

        self.centroid = self._calc_centroid()
        self.area = self._calc_area()
        self.perimeter = self._calc_perimeter()

        # The smaller the rounder, the bigger the flatter
        self.compactness = self.perimeter ** 2 / (4 * math.pi * self.area)

        # used for bound rectangular
        self.boundingRect = self._calc_rect()

        self.path = [self.centroid]
        self.path_length = 0


        # Approximate polygon, can obtain how many edges, thus judging the shape
        self.shape = self._calc_shape()
        # The largest inner ellipse (major axis, minor axis) is diameter
        # self.axis = self._calc_axis()

    def _calc_centroid(self):
        mom = cv2.moments(self.contour)
        centroid = (int(mom['m10'] / mom['m00']), int(mom['m01'] / mom['m00']))  # 使用前三个矩m00, m01和m10计算重心
        return centroid

    def _calc_area(self):
        return cv2.contourArea(self.contour)

    def _calc_perimeter(self):
        return cv2.arcLength(self.contour, True)

    def _calc_shape(self):
        # This precision represents the maximum distance between the original curve and the approximate curve
        epsilon = 0.01 * cv2.arcLength(self.contour, True)
        return cv2.approxPolyDP(self.contour, epsilon, True)

    def _calc_axis(self):
        # (x, y)The centroid of inner ellipse
        # (a, b)Major axis and minor axis
        # angle Angle of rotation at the center of the inner ellipse
        (x, y), (a, b), angle = cv2.fitEllipse(self.contour)
        axis = (a, b)
        return axis

    def _calc_rect(self):
        x, y, w, h = cv2.boundingRect(self.contour)
        return x, y, w, h

    def split_detection(self):
        max_compactness = 1.45
        if self.compactness > max_compactness:
            return True
        else:
            return False

    def register_id(self, ID, color=()):
        if color:
            self.segmentation_color = color
        self.id = ID

    def update_path(self, pre_path):
        self.path = pre_path
        first = self.path[-1]
        self.path_length += math.sqrt((self.centroid[0] - first[0])*(self.centroid[0] - first[0])+(self.centroid[1] - first[1])*(self.centroid[1] - first[1]))
        self.path.append(self.centroid)

    def get_path(self):
        return self.path

    def get_id(self):
        return self.id

    def get_centroid(self):
        return self.centroid

    def get_color(self):
        return self.segmentation_color

    def get_area(self):
        return self.area

    def get_bounding_rect(self):
        return self.boundingRect
